import { mat4, vec3, vec4 } from 'gl-matrix';
import { Renderer } from './renderer';
import { createGPUBuffer } from './utils';

export class Camera {
  renderer: Renderer;
  radius: number;
  forwardVector: vec4;
  upVector: vec4;
  viewDir: vec3;

  modelViewMatrixInverse: mat4;
  modelViewMatrixUniformBuffer: GPUBuffer;

  normalMatrix: mat4;
  normalMatrixUniformBuffer: GPUBuffer;

  viewDirectionUniformBuffer: GPUBuffer;
  lightDirectionUniformBuffer: GPUBuffer;

  projectionMatrix: mat4;
  projectionMatrixUniformBuffer: GPUBuffer;

  lightProjectionMatrix: mat4;
  lightProjectionMatrixUniformBuffer: GPUBuffer;
  lightModelViewMatrixUniformBuffer: GPUBuffer;

  constructor(renderer: Renderer) {
    this.renderer = renderer;
    this.radius = 15.0;

    this.forwardVector = vec4.fromValues(0.0, this.radius, 10.0, 0.0);
    this.upVector = vec4.fromValues(0.0, 1.0, 0.0, 0.0);

    this.modelViewMatrixUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.modelViewMatrix as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );

    this.viewDir = vec3.fromValues(-10.0, -10.0, -10);

    this.viewDirectionUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.viewDir as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );

    this.lightDirectionUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.viewDir as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );

    this.modelViewMatrixInverse = mat4.invert(mat4.create(), this.modelViewMatrix);

    this.normalMatrix = mat4.transpose(mat4.create(), this.modelViewMatrixInverse);

    this.normalMatrixUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.normalMatrix as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );

    this.projectionMatrix = mat4.perspective(mat4.create(), 1.4, 640.0 / 480.0, 0.1, 1000.0);

    this.projectionMatrixUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.projectionMatrix as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );

    this.lightProjectionMatrix = mat4.perspective(
      mat4.create(),
      Math.acos(0.9) * 2.0,
      1.0,
      1.0,
      100.0
    );

    this.lightProjectionMatrixUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.lightProjectionMatrix as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );
    this.lightModelViewMatrixUniformBuffer = createGPUBuffer(
      this.renderer.device,
      this.modelViewMatrix as Float32Array,
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );
  }

  get modelViewMatrix() {
    return mat4.lookAt(
      mat4.create(),
      vec3.fromValues(this.forwardVector[0], this.forwardVector[1], this.forwardVector[2]),
      vec3.fromValues(0, 0, 0),
      vec3.fromValues(this.upVector[0], this.upVector[1], this.upVector[2])
    );
  }

  update() {
    const { device, angle } = this.renderer;
    // 更新相机视图矩阵buffer
    device.queue.writeBuffer(
      this.modelViewMatrixUniformBuffer,
      0,
      this.modelViewMatrix as Float32Array
    );
    // 根据视图矩阵生产法线矩阵buffer
    const modelViewMatrixInverse = mat4.invert(mat4.create(), this.modelViewMatrix);
    const normalMatrix = mat4.transpose(mat4.create(), modelViewMatrixInverse);

    device.queue.writeBuffer(this.normalMatrixUniformBuffer, 0, normalMatrix as Float32Array);

    // 观察方向从相机原点出发，指向相机本地z轴
    const viewDir = vec3.fromValues(
      this.forwardVector[0],
      this.forwardVector[1],
      this.forwardVector[2]
    );
    device.queue.writeBuffer(this.viewDirectionUniformBuffer, 0, viewDir as Float32Array);

    // TODO 光照方向放到light中
    // 光照方向沿着z轴转圈，半径8，z为10
    const lightDir = vec3.fromValues(Math.cos(angle) * 8.0, 10, Math.sin(angle) * 8.0);
    device.queue.writeBuffer(this.lightDirectionUniformBuffer, 0, lightDir as Float32Array);

    // TODO 将光线的矩阵放到light文件中
    const lightModelViewMatrix = mat4.lookAt(
      mat4.create(),
      vec3.fromValues(Math.cos(angle) * 8.0, 10, Math.sin(angle) * 8.0),
      vec3.fromValues(0, 0, 0),
      vec3.fromValues(0.0, 1.0, 0.0)
    );
    device.queue.writeBuffer(
      this.lightModelViewMatrixUniformBuffer,
      0,
      lightModelViewMatrix as Float32Array
    );
  }
}
